#pragma once

#include "isNumber.hpp"
#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

class ByteStream : protected std::vector<uint8_t> {
#define U32(x) static_cast<uint32_t>(x)
public:
    using const_iterator = std::vector<uint8_t>::const_iterator;
    using iterator = std::vector<uint8_t>::iterator;

    ByteStream()
        : std::vector<uint8_t>(){};

    template <typename T, typename std::enable_if<IsNumberTypeValue<T>, int>::type = 0>
    ByteStream(const T &v);

    ByteStream(const std::initializer_list<uint8_t> &list)
        : std::vector<uint8_t>(list){};

    ByteStream(const std::string &str)
        : std::vector<uint8_t>(str.begin(), str.end()){};

    ByteStream(const_iterator begin, const_iterator end)
        : std::vector<uint8_t>(begin, end){};

    ByteStream(uint8_t *begin, std::size_t len)
        : std::vector<uint8_t>(begin, begin + len){};

    ByteStream(const ByteStream &) = default;
    ByteStream(ByteStream &&) noexcept = default;
    ByteStream &operator=(const ByteStream &) = default;
    ByteStream &operator=(ByteStream &&) noexcept = default;

    ByteStream &fromHexString(const std::string &hex) {
        if (hex.length() % 2 != 0) {
            std::cerr << "hex string length must be even" << std::endl;
            return *this;
        }

        for (size_t i = 0; i < hex.length(); i += 2) {
            std::string byteString = hex.substr(i, 2);
            emplace_back(static_cast<uint8_t>(std::stoul(byteString, nullptr, 16)));
        }

        return *this;
    }

public:
    template <typename T>
    inline static constexpr bool canGetTypeValue = IsNumberTypeValue<T> || std::is_same<T, std::string>::value;

public:
    ByteStream lastNumBytes(int num) const;

    template <typename T = uint32_t, std::enable_if_t<canGetTypeValue<T>, int> = 0>
    T get() const {
        if constexpr (std::is_same<T, std::string>::value) {
            return std::string(begin(), end());
        } else {
            T ret{};
            auto data = lastNumBytes(std::min(size(), sizeof(T)));

            for (auto it = data.begin(); it != data.end(); it++) {
                ret |= (*it);
                if (std::next(it) != data.end()) {
                    ret <<= 8;
                }
            }

            return ret;
        }
    }

public:
    template <typename T, std::enable_if_t<canGetTypeValue<T>, void *> = nullptr>
    bool operator==(const T &v) { return get<T>() == v; }

    bool operator==(const ByteStream &b) const { return ((std::vector<uint8_t> &)*this) == b; }

    bool operator!=(const ByteStream &b) const { return ((std::vector<uint8_t> &)*this) != b; }

    bool beginWith(const ByteStream &prefix) const {
        if (prefix.size() > size()) {
            return false;
        }
        return std::equal(prefix.begin(), prefix.end(), begin());
    }

    ByteStream &operator<<(uint8_t v) {
        emplace_back(v);
        return *this;
    }
    ByteStream &operator<<(const ByteStream &v) {
        insert(end(), v.begin(), v.end());
        return *this;
    }
    typename std::vector<uint8_t>::iterator begin(void) { return ((std::vector<uint8_t> &)*this).begin(); }
    typename std::vector<uint8_t>::const_iterator begin(void) const { return ((std::vector<uint8_t> &)*this).cbegin(); }
    typename std::vector<uint8_t>::iterator end(void) { return ((std::vector<uint8_t> &)*this).end(); }
    typename std::vector<uint8_t>::const_iterator end(void) const { return ((std::vector<uint8_t> &)*this).cend(); }

    const uint8_t operator[](std::size_t index) const { return ((std::vector<uint8_t> &)*this).operator[](index); }
    uint8_t &operator[](std::size_t index) { return ((std::vector<uint8_t> &)*this).operator[](index); }

    ByteStream operator+(const ByteStream &other) const {
        ByteStream data(*this);
        data.insert(data.end(), other.begin(), other.end());
        return data;
    }

    void pop_back(void) { ((std::vector<uint8_t> &)*this).pop_back(); }
    void push_back(uint8_t v) { ((std::vector<uint8_t> &)*this).push_back(v); }

    void pushFront(uint8_t v) { insert(begin(), v); }

    ByteStream pushFront(uint8_t v) const {
        ByteStream temp = *this;
        temp.insert(temp.begin(), v);
        return temp;
    }

    bool empty(void) const { return ((std::vector<uint8_t> &)*this).empty(); }

    const uint8_t *data(void) const { return ((std::vector<uint8_t> &)*this).data(); }

    uint8_t *data(void) { return ((std::vector<uint8_t> &)*this).data(); }

    std::size_t length(void) const { return ((std::vector<uint8_t> &)*this).size(); }
    std::size_t size(void) const { return length(); }

    friend ByteStream operator+(const std::initializer_list<uint8_t> &list, const ByteStream &data) {
        ByteStream d(list);
        d.insert(d.end(), data.begin(), data.end());
        return d;
    }

    friend std::ostream &operator<<(std::ostream &out, const ByteStream &b);

    const std::string toHexString(void) const {
        std::stringstream out;
        for (auto it = begin(); it != end(); it++) {
            out << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(*it);
        }
        return out.str();
    }

    const std::string toDecString(void) const {
        std::stringstream ss;
        for (const auto &item : *this) {
            ss << std::dec << std::setw(2) << std::setfill('0') << U32(item);
        }

        return ss.str();
    }

    const std::string format(const std::string &interval = " ") const {
        std::stringstream out;
        out << "[";
        for (auto it = begin(); it != end(); it++) {
            out << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(*it);
            if (std::next(it) != end()) {
                out << interval;
            }
        }
        out << "]";
        return out.str();
    };

    ByteStream toByteOrder(const std::string &order) const {
        if (size() < order.length()) {
            return *this;
        }

        const std::map<char, uint8_t> orderTable = {
            {'a', 0},
            {'A', 0},
            {'b', 1},
            {'B', 1},
            {'c', 2},
            {'C', 2},
            {'d', 3},
            {'D', 3},
            {'e', 4},
            {'E', 4},
            {'f', 5},
            {'F', 5},
            {'g', 6},
            {'G', 6},
            {'h', 7},
            {'H', 7},
        };

        ByteStream data;
        for (const auto &c : order) {
            data.push_back((*this)[orderTable.at(c)]);
        }

        return data;
    }

    template <typename T, typename = std::enable_if_t<IsNumberTypeValue<T>>>
    ByteStream getBits(std::size_t begin, std::size_t end) const {
        if (end < begin || end > size() * 8 - 1) {
            return {};
        }

        auto mask = (1 << (end - begin + 1)) - 1;
        mask <<= begin;
        return ((get<T>() & mask) >> begin);
    }

    ByteStream subBytes(std::size_t b, std::size_t e) const {
        if (e < b || e > size() - 1) {
            return {};
        }

        return ByteStream(begin() + b, begin() + b + (e - b + 1));
    }

    ByteStream &removeByteBack(uint8_t byte) {
        if (back() == byte) {
            pop_back();
            return removeByteBack(byte);
        }
        return *this;
    }

    ByteStream &reverse(void) {
        std::reverse(begin(), end());
        return *this;
    }

    static void test(void);
};

template <typename T, typename std::enable_if<IsNumberTypeValue<T>, int>::type>
ByteStream::ByteStream(const T &v) {
    reserve(sizeof(T));
    for (int i = sizeof(T) - 1; i >= 0; --i) {
        emplace_back((v >> (i * 8)) & 0xff);
    }
}

inline ByteStream ByteStream::lastNumBytes(int num) const {
    if (num > size()) {
        return *this;
    }

    return ByteStream(begin() + size() - num, end());
}

inline std::ostream &operator<<(std::ostream &out, const ByteStream &b) {
    out << "[";
    for (auto it = b.begin(); it != b.end(); it++) {
        out << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(*it);
        if (std::next(it) != b.end()) {
            out << " ";
        }
    }
    return out << "]";
}

inline void ByteStream::test(void) {
    {
        ByteStream a((uint8_t)(0x1));
        std::cout << a << std::endl;
        ByteStream b((int8_t)0x1);
        std::cout << b << std::endl;
    }
    {
        ByteStream a((uint16_t)(0x1234));
        std::cout << a << std::endl;
        ByteStream b((int16_t)0x1234);
        std::cout << b << std::endl;
    }
    {
        ByteStream a((uint32_t)(0x12345678));
        std::cout << a << std::endl;
        ByteStream b((int32_t)0x12345678);
        std::cout << b << std::endl;
    }
    {
        ByteStream a((uint64_t)(0x1234567890123456));
        std::cout << a << std::endl;
        ByteStream b((int64_t)0x1234567890123456);
        std::cout << b << std::endl;
    }

    {
        ByteStream a({0x12, 0x34, 0x56, 0x78});
        std::cout << a << std::endl;
    }

    {
        ByteStream a("01234567890");
        std::cout << a << std::endl;

        std::cout << "last --> " << std::hex << a.lastNumBytes(6).get<std::string>() << std::endl;

        ByteStream b(a);
        std::cout << b << std::endl;

        ByteStream c(ByteStream("123456789"));
        std::cout << c << std::endl;

        auto d = c;
        std::cout << d << std::endl;
    }
}

using Data = ByteStream;
